/**
 * 
 */
package com.rpg.base.flyweight;

import com.arx.interactive.InteractiveObject;
import com.arx.script.flyweight.ScriptObject;
import com.dalonedrau.pool.PooledStringBuilder;
import com.dalonedrau.pool.StringBuilderPool;
import com.module.hq.bus.flyweights.HQScriptTimer;
import com.module.hq.bus.globals.HQGlobals;
import com.rpg.base.globals.RPGGlobals;

/**
 * @author Donald
 * @param <IO>
 */
public class Scriptable<IO> {
	/** bit flag storing which events are allowed. */
	private long				allowevents;
	/** the IO associated with this script. */
	private IO					io;
	/** the array of local {@link ScriptVariable}s. */
	private ScriptVariable[]	lvar	= new ScriptVariable[0];
	private Scriptable<IO>		master;
	private int[] timers = new int[HQGlobals.MAX_SCRIPTTIMERS];
	public final int getTimer(final int index) {
		return timers[index];
	}
	public final void setTimer(final int index, final int val) {
		timers[index] = val;
	}
	/** the number of local variables. */
	private int					numberOfLocalVars;
	/**
	 * Adds a local variable.
	 * @param svar the local variable
	 */
	public final void addLocalVariable(final ScriptVariable svar) {
		int index = -1;
		for (int i = 0; i < lvar.length; i++) {
			if (lvar[i] == null) {
				index = i;
				break;
			}
		}
		if (index == -1) {
			index = lvar.length;
			ScriptVariable[] dest = new ScriptVariable[lvar.length + 1];
			System.arraycopy(lvar, 0, dest, 0, lvar.length);
			lvar = dest;
			dest = null;
		}
		lvar[index] = svar;
		numberOfLocalVars++;
	}
	/**
	 * Assigns a bit flag for an allowed event.
	 * @param event the event flag
	 */
	public final void assignAllowedEvent(final int event) {
		allowevents |= event;
	}
	/** Clears the bit flags for allowed events. */
	public final void clearAllowedEvents() {
		allowevents = 0;
	}
	/** Clears all local variables assigned to the {@link Scriptable}. */
	public final void clearLocalVariables() {
		numberOfLocalVars = 0;
		if (lvar != null) {
			for (int i = 0; i < lvar.length; i++) {
				if (lvar[i] != null) {
					lvar[i].clear();
				}
				lvar[i] = null;
			}
		}
	}
	/** Extends the size of the local variables array if needed. */
	private void extendLvarArray() {
		if (lvar.length < numberOfLocalVars + 1) {
			ScriptVariable[] temp = new ScriptVariable[numberOfLocalVars + 1];
			System.arraycopy(lvar, 0, temp, 0, lvar.length);
			lvar = temp;
			temp = null;
		}
	}
	/**
	 * Gets the IO associated with this script.
	 * @return {@link IO}
	 */
	public final IO getIO() {
		return io;
	}
	/**
	 * Gets a local {@link Scriptable} variable.
	 * @param index the index of the variable
	 * @return {@link ScriptVariable}
	 */
	public final ScriptVariable getLocalVariable(final int index) {
		ScriptVariable svar = null;
		if (lvar != null
				&& index >= 0
				&& index < lvar.length) {
			svar = lvar[index];
		}
		return svar;
	}
	/**
	 * Gets a local {@link Scriptable} variable.
	 * @param index the index of the variable
	 * @return {@link ScriptVariable}
	 */
	public final ScriptVariable getLocalVariable(final String name) {
		ScriptVariable svar = null;
		if (lvar != null) {
			for (int i = 0; i < lvar.length; i++) {
				if (lvar[i].getName().equalsIgnoreCase(name)) {
					svar = lvar[i];
					break;
				}
			}
		}
		return svar;
	}
	/**
	 * Gets the master
	 * @return {@link Scriptable<IO>}
	 */
	public Scriptable<IO> getMaster() {
		return master;
	}
	/**
	 * Gets the number of local variables.
	 * @return int
	 */
	public final int getNumberOfLocalVars() {
		return numberOfLocalVars;
	}
	/**
	 * Determines if the {@link InteractiveObject} allows a specific event.
	 * @param event the event flag
	 * @return true if the object has the event set; false otherwise
	 */
	public final boolean hasAllowedEvent(final int event) {
		return (allowevents & event) == event;
	}
	/**
	 * Determines if a {@link ScriptObject} has local variables assigned to it.
	 * @return true if the {@link ScriptObject} has local variables; false
	 *         otherwise
	 */
	public final boolean hasLocalVariables() {
		return lvar != null && numberOfLocalVars > 0;
	}
	/** Script run when the {@link Scriptable} is added to a party. */
	public int onAddToParty() throws Exception {
		return HQGlobals.ACCEPT;
	}
	/** Script run when the {@link Scriptable} is a target of aggression. */
	public int onAggression() throws Exception {
		return HQGlobals.ACCEPT;
	}
	/**
	 * On IO initialization.
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	public int onInit() throws Exception {
		return HQGlobals.ACCEPT;
	}
	/**
	 * On IO goes into inventory.
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	public int onInventoryIn() throws Exception {
		return HQGlobals.ACCEPT;
	}
	/**
	 * On IO initialization end.
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	public int onInitEnd() throws Exception {
		return HQGlobals.ACCEPT;
	}
	/**
	 * On IO traveling on the game map.
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	public int onMovement() throws Exception {
		return HQGlobals.ACCEPT;
	}
	/**
	 * On IO unequipped.
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	public int onUnequip() throws Exception {
		return HQGlobals.ACCEPT;
	}
	/**
	 * Removed an event from the list of allowed events.
	 * @param event the event flag
	 */
	public final void removeAllowedEvent(final int event) {
		allowevents = allowevents & ~event;
	}
	/**
	 * Sets the IO associated with this script.
	 * @param val the IO to set
	 */
	public final void setIO(final IO val) {
		this.io = val;
	}
	/**
	 * Sets a local {@link ScriptVariable}.
	 * @param index the index of the variable
	 * @param svar the local {@link ScriptVariable}
	 */
	public final void setLocalVariable(final ScriptVariable svar) {
		boolean found = false;
		for (int i = 0; i < lvar.length; i++) {
			if (lvar[i].getName().equalsIgnoreCase(svar.getName())) {
				lvar[i] = svar;
				found = true;
				break;
			}
		}
		// if the index number is valid
		if (!found) {
			extendLvarArray();
			lvar[numberOfLocalVars++] = svar;
			numberOfLocalVars++;
		}
	}
	/**
	 * Sets a local {@link ScriptVariable}.
	 * @param index the index of the variable
	 * @param svar the local {@link ScriptVariable}
	 */
	public final void setLocalVariable(final int index,
			final ScriptVariable svar) {
		// if the index number is valid
		if (index >= 0) {
			// if the local variables array needs to be extended, do so
			if (index >= numberOfLocalVars) {
				numberOfLocalVars = index + 1;
			}
			extendLvarArray();
			// copy the variable into the local variables arrays
			lvar[index] = svar;
		}
	}
	/**
	 * Sets a global variable.
	 * @param name the name of the global variable
	 * @param value the variable's value
	 * @throws Exception if an error occurs
	 */
	public void setLocalVariable(final String name, final Object value)
			throws Exception {
		if (lvar == null) {
			lvar = new ScriptVariable[0];
		}
		boolean found = false;
		for (int i = 0; i < lvar.length; i++) {
			ScriptVariable var = lvar[i];
			if (var.getName().equals(name)) {
				// found the correct script variable
				if (var.getType() == RPGGlobals.TYPE_L_07_TEXT) {
					if (value instanceof String) {
						var.setText((String) value);
					} else if (value instanceof Integer) {
						var.setText(((Integer) value).toString());
					} else if (value instanceof Long) {
						var.setText(((Long) value).toString());
					} else if (value instanceof Float) {
						var.setText(((Float) value).toString());
					} else {
						PooledStringBuilder sb =
								StringBuilderPool.getInstance()
										.getStringBuilder();
						sb.append("Local variable ");
						sb.append(name);
						sb.append(" was passed new value of type ");
						sb.append(value.getClass().getCanonicalName());
						sb.append(". Only String, Integer, Long, or Float ");
						sb.append("allowed.");
						Exception ex = new Exception(sb.toString());
						sb.returnToPool();
						throw ex;
					}
				} else if (var.getType() == RPGGlobals.TYPE_L_08_FLOAT) {
					if (value instanceof String) {
						var.setFloatVal(Float.parseFloat((String) value));
					} else if (value instanceof Integer) {
						var.setFloatVal(((Integer) value).floatValue());
					} else if (value instanceof Long) {
						var.setFloatVal(((Long) value).floatValue());
					} else if (value instanceof Float) {
						var.setFloatVal((Float) value);
					} else {
						PooledStringBuilder sb =
								StringBuilderPool.getInstance()
										.getStringBuilder();
						sb.append("Local variable ");
						sb.append(name);
						sb.append(" was passed new value of type ");
						sb.append(value.getClass().getCanonicalName());
						sb.append(". Only String, Integer, Long, or Float ");
						sb.append("allowed.");
						Exception ex = new Exception(sb.toString());
						sb.returnToPool();
						throw ex;
					}
				} else if (var.getType() == RPGGlobals.TYPE_L_09_FLOAT_ARR) {
					if (value instanceof String) {
						var.setFloatArrayVal(Float.parseFloat((String) value));
					} else if (value instanceof Integer) {
						var.setFloatArrayVal(((Float) value).floatValue());
					} else if (value instanceof Long) {
						var.setFloatArrayVal(((Long) value).floatValue());
					} else if (value instanceof Float) {
						var.setFloatArrayVal((Float) value);
					} else if (value instanceof int[]) {
						for (int c = 0; c < ((int[]) value).length; c++) {
							var.setFloatArrayVal(c, ((int[]) value)[c]);
						}
					} else if (value instanceof long[]) {
						for (int c = 0; c < ((long[]) value).length; c++) {
							var.setFloatArrayVal(c, ((long[]) value)[c]);
						}
					} else if (value instanceof float[]) {
						for (int c = 0; c < ((float[]) value).length; c++) {
							var.setFloatArrayVal(c, ((float[]) value)[c]);
						}
					} else {
						PooledStringBuilder sb =
								StringBuilderPool.getInstance()
										.getStringBuilder();
						sb.append("Local variable ");
						sb.append(name);
						sb.append(" was passed new value of type ");
						sb.append(value.getClass().getCanonicalName());
						sb.append(". Only String, Integer, int[], long[], ");
						sb.append("float[] or Float allowed.");
						Exception ex = new Exception(sb.toString());
						sb.returnToPool();
						throw ex;
					}
				} else if (var.getType() == RPGGlobals.TYPE_L_10_INT) {
					if (value instanceof String) {
						var.setIntVal(Integer.parseInt((String) value));
					} else if (value instanceof Integer) {
						var.setIntVal((Integer) value);
					} else if (value instanceof Long) {
						var.setIntVal(((Long) value).intValue());
					} else if (value instanceof Float) {
						var.setIntVal(((Float) value).intValue());
					} else {
						PooledStringBuilder sb =
								StringBuilderPool.getInstance()
										.getStringBuilder();
						sb.append("Local variable ");
						sb.append(name);
						sb.append(" was passed new value of type ");
						sb.append(value.getClass().getCanonicalName());
						sb.append(". Only String, Integer, Long, or Float ");
						sb.append("allowed.");
						Exception ex = new Exception(sb.toString());
						sb.returnToPool();
						throw ex;
					}
				} else if (var.getType() == RPGGlobals.TYPE_L_11_INT_ARR) {
					if (value instanceof String) {
						var.setIntArrayVal(Integer.parseInt((String) value));
					} else if (value instanceof Float) {
						var.setIntArrayVal(((Float) value).intValue());
					} else if (value instanceof float[]) {
						for (int c = 0; c < ((float[]) value).length; c++) {
							var.setIntArrayVal(c, (int) ((float[]) value)[c]);
						}
					} else if (value instanceof Integer) {
						var.setIntArrayVal((Integer) value);
					} else if (value instanceof int[]) {
						for (int c = 0; c < ((int[]) value).length; c++) {
							var.setIntArrayVal(c, ((int[]) value)[c]);
						}
					} else if (value instanceof Long) {
						var.setIntArrayVal(((Long) value).intValue());
					} else if (value instanceof long[]) {
						for (int c = 0; c < ((long[]) value).length; c++) {
							var.setIntArrayVal(c, (int) ((long[]) value)[c]);
						}
					} else {
						PooledStringBuilder sb =
								StringBuilderPool.getInstance()
										.getStringBuilder();
						sb.append("Local variable ");
						sb.append(name);
						sb.append(" was passed new value of type ");
						sb.append(value.getClass().getCanonicalName());
						sb.append(". Only String, Float, float[], Integer, ");
						sb.append("int[], Long, or long[] allowed.");
						Exception ex = new Exception(sb.toString());
						sb.returnToPool();
						throw ex;
					}
				} else if (var.getType() == RPGGlobals.TYPE_L_12_LONG) {
					if (value instanceof String) {
						var.setLongVal(Long.parseLong((String) value));
					} else if (value instanceof Integer) {
						var.setLongVal(((Integer) value).longValue());
					} else if (value instanceof Long) {
						var.setLongVal((Long) value);
					} else if (value instanceof Float) {
						var.setLongVal(((Float) value).longValue());
					} else {
						PooledStringBuilder sb =
								StringBuilderPool.getInstance()
										.getStringBuilder();
						sb.append("Local variable ");
						sb.append(name);
						sb.append(" was passed new value of type ");
						sb.append(value.getClass().getCanonicalName());
						sb.append(". Only String, Integer, Long, or Float ");
						sb.append("allowed.");
						Exception ex = new Exception(sb.toString());
						sb.returnToPool();
						throw ex;
					}
				} else if (var.getType() == RPGGlobals.TYPE_L_13_LONG_ARR) {
					if (value instanceof String) {
						var.setLongArrayVal(Long.parseLong((String) value));
					} else if (value instanceof Float) {
						var.setLongArrayVal(((Float) value).longValue());
					} else if (value instanceof float[]) {
						for (int c = 0; c < ((float[]) value).length; c++) {
							var.setLongArrayVal(c, (long) ((float[]) value)[c]);
						}
					} else if (value instanceof Integer) {
						var.setLongArrayVal(((Integer) value).longValue());
					} else if (value instanceof int[]) {
						for (int c = 0; c < ((int[]) value).length; c++) {
							var.setLongArrayVal(c, ((int[]) value)[c]);
						}
					} else if (value instanceof Long) {
						var.setLongArrayVal((Long) value);
					} else if (value instanceof long[]) {
						for (int c = 0; c < ((long[]) value).length; c++) {
							var.setLongArrayVal(c, ((long[]) value)[c]);
						}
					} else {
						PooledStringBuilder sb =
								StringBuilderPool.getInstance()
										.getStringBuilder();
						sb.append("Local variable ");
						sb.append(name);
						sb.append(" was passed new value of type ");
						sb.append(value.getClass().getCanonicalName());
						sb.append(". Only String, Float, float[], Integer, ");
						sb.append("int[], Long, or long[] allowed.");
						Exception ex = new Exception(sb.toString());
						sb.returnToPool();
						throw ex;
					}
				}
				found = true;
				break;
			}
		}
		if (!found) {
			// create a new variable and add to the global array
			ScriptVariable var = null;
			if (value instanceof String) {
				var = 
					new ScriptVariable(name, RPGGlobals.TYPE_L_07_TEXT, value);
			} else if (value instanceof Float) {
				var = 
					new ScriptVariable(name, RPGGlobals.TYPE_L_08_FLOAT, value);
			} else if (value instanceof float[]) {
				var = new ScriptVariable(
						name, RPGGlobals.TYPE_L_09_FLOAT_ARR, value);
			} else if (value instanceof Integer) {
				var = new ScriptVariable(name, RPGGlobals.TYPE_L_10_INT, value);
			} else if (value instanceof int[]) {
				var = new ScriptVariable(
						name, RPGGlobals.TYPE_L_11_INT_ARR, value);
			} else if (value instanceof Long) {
				var = 
					new ScriptVariable(name, RPGGlobals.TYPE_L_12_LONG, value);
			} else if (value instanceof long[]) {
				var = new ScriptVariable(
						name, RPGGlobals.TYPE_L_13_LONG_ARR, value);
			} else {
				PooledStringBuilder sb =
						StringBuilderPool.getInstance().getStringBuilder();
				sb.append("Local variable ");
				sb.append(name);
				sb.append(" was passed new value of type ");
				sb.append(value.getClass().getCanonicalName());
				sb.append(". Only String, Float, float[], Integer, int[], ");
				sb.append("Long, or long[] allowed.");
				Exception ex = new Exception(sb.toString());
				sb.returnToPool();
				throw ex;
			}
			ScriptVariable[] dest = new ScriptVariable[lvar.length + 1];
			System.arraycopy(lvar, 0, dest, 0, lvar.length);
			dest[lvar.length] = var;
			lvar = dest;
			dest = null;
		}
	}
	/**
	 * Sets the master
	 * @param master the master to set
	 */
	public void setMaster(Scriptable<IO> master) {
		this.master = master;
	}
	/**
	 * Sets the number of local variables.
	 * @param val the number to set
	 */
	public final void setNumberOfLocalVars(final int val) {
		numberOfLocalVars = val;
		if (lvar.length < numberOfLocalVars) {
			ScriptVariable[] temp = new ScriptVariable[numberOfLocalVars];
			System.arraycopy(lvar, 0, temp, 0, lvar.length);
			lvar = temp;
			temp = null;
		}
	}
}
